1 module targets.psvita; 2 import std.exception; 3 import features.git; 4 import commons; 5 import std.path; 6 7 private enum updateCmd = "sudo apt-get update"; 8 private enum depsInstallCmd = "sudo apt-get install make git-core cmake python3 curl wget bzip2"; 9 private enum vdpmRepo = "https://github.com/vitasdk/vdpm"; 10 private enum bootstrapVsdk = "./bootstrap-vitasdk.sh"; 11 private enum installAllVsdk = "./install-all.sh"; 12 13 version(Posix) 14 private string vitaSdkPath="/usr/local/vitasdk"; 15 else 16 private string vitaSdkPath; 17 18 private enum vitasdkExports = 19 "\n"~"export PATH=$VITASDK/bin:$PATH #adds vitasdk tool to $PATH"; 20 21 private enum vdpmInstallCmd = 22 "git clone https://github.com/vitasdk/vdpm && "~ 23 "cd vdpm && "~ 24 "./bootstrap-vitasdk-.sh "~ 25 "./install-all.sh"; 26 27 28 private bool setupPsvitaLinux(ref Terminal t, ref RealTimeConsoleInput input) 29 { 30 string exports = "\n"~"export VITASDK="~vitaSdkPath ~ vitasdkExports; 31 std.file.append(buildPath(environment["HOME"], ".bashrc"), exports); 32 t.wait(spawnShell(updateCmd)); 33 t.wait(spawnShell(depsInstallCmd)); 34 t.wait(spawnShell(vdpmInstallCmd)); 35 t.wait(spawnShell("vitasdk-update")); 36 return true; 37 } 38 private string getWslSource() 39 { 40 return executeShell("wsl echo -n $(wslpath \"%USERPROFILE%\")/.bashrc").output; 41 } 42 43 private string getWslPath(string inputPath) 44 { 45 import std.path; 46 import std.string:replace, toLower; 47 import std.conv; 48 return "/mnt/"~driveName(inputPath)[0].toLower.to!string~replace(inputPath[2..$], '\\', '/'); 49 } 50 51 private auto getExecFunc(ref Terminal t) 52 { 53 return (scope string arg) 54 { 55 version(Windows) 56 { 57 string toExec = "wsl source "~getWslSource~" ^&^& "~arg; 58 t.writelnHighlighted("Executing: ", toExec); 59 return wait(spawnShell(toExec)); 60 } 61 else 62 { 63 t.writelnHighlighted("Executing: ", arg); 64 return wait(spawnShell(arg)); 65 } 66 }; 67 } 68 69 /** 70 * The first build will simply generate the VPK, by mapping assets, compiling 71 * all the required stuff for PSVita, and also including the main binary (eboot.bin) 72 * after the first build is done, one must manually install the hipreme_engine.vpk 73 * by using the vitashell utility. 74 * Params: 75 * t = 76 * Returns: 77 */ 78 private bool firstBuild(ref Terminal t) 79 { 80 with(WorkingDir(getHipPath("build", "vita", "hipreme_engine"))) 81 { 82 t.flush; 83 auto exec = getExecFunc(t); 84 if(exec("make") != 0) 85 { 86 t.writelnError("Make failed."); 87 return false; 88 } 89 if(exec("curl ftp://"~configs["psvIp"].str~":1337/ux0:/ -T ./hipreme_engine.vpk") != 0) 90 { 91 t.writelnError("Could not send the VPK."); 92 return false; 93 } 94 return true; 95 } 96 } 97 98 /** 99 * Instead of building the entire VPK for PS Vita, it only changes the binary, after that, 100 * it directly sends this new binary to the Package folder, so, there will be no need 101 * to extract a VPK by going into the Install Process again, making it a lot faster 102 * to both build and test. 103 * 104 * For even faster installation, it is recomended to run a background FTP on PSV 105 * Params: 106 * t = 107 * Returns: 108 */ 109 private bool fastBuild(ref Terminal t) 110 { 111 enum APP_ID = "VSDK00007"; 112 auto exec = getExecFunc(t); 113 114 with(WorkingDir(getHipPath("build", "vita", "hipreme_engine"))) 115 { 116 version(Windows) enum pipe = "^|"; 117 else enum pipe = "|"; 118 119 // exec("make clean"); 120 if(exec("make eboot.bin") != 0) 121 { 122 t.writelnError("Could not rebuild."); 123 return false; 124 } 125 // exec("echo screen on "~pipe~" nc "~configs["psvIp"].str~" "~configs["psvCmdPort"].str); 126 if(exec("curl ftp://"~configs["psvIp"].str~":1337/ux0:/app/"~APP_ID~"/ -T ./eboot.bin") != 0) 127 { 128 t.writelnError("Could not send eboot.bin"); 129 return false; 130 } 131 // exec("echo launch "~APP_ID~" "~pipe~" nc "~configs["psvIp"].str~" "~configs["psvCmdPort"].str); 132 return true; 133 } 134 } 135 136 private bool setupPsvitaWindows(ref Terminal t, ref RealTimeConsoleInput input) 137 { 138 if(executeShell("where wsl").status != 0) 139 { 140 t.writelnError("Please, run a command prompt with administrator access and run `wsl --install` before developing for PSV on Windows."); 141 return false; 142 } 143 string vitaPath = getHipPath("tools", "hbuild", "PSVita"); 144 145 146 string bashRc = buildPath(environment["USERPROFILE"], ".bashrc"); 147 string fileToSource = getWslSource(); 148 149 vitaSdkPath = getWslPath(buildPath(vitaPath, "vitasdk")); 150 151 152 153 string exports = "\n"~"export VITASDK="~vitaSdkPath~vitasdkExports; 154 if(std.file.exists(bashRc)) 155 { 156 import std.algorithm.searching:countUntil; 157 string data = std.file.readText(bashRc); 158 if(countUntil(data, "VITASDK") == -1) 159 std.file.append(bashRc, exports); 160 } 161 else std.file.append(bashRc, exports); 162 163 auto wslExec = (scope string[] commands...) 164 { 165 import std.array:join; 166 t.writelnHighlighted("WSL Execution: "~commands); 167 return t.wait(spawnShell("wsl source "~fileToSource~" ^&^& "~join(commands, " "))); 168 }; 169 170 if(wslExec(updateCmd) != 0) 171 { 172 t.writelnError("Could not update system repositores"); 173 return false; 174 } 175 if(wslExec(depsInstallCmd) != 0) 176 { 177 t.writelnError("Could not setup vita dependencies"); 178 return false; 179 } 180 with(WorkingDir(vitaPath)) 181 { 182 if(!std.file.exists("vdpm")) 183 { 184 if(wslExec("git clone "~vdpmRepo) != 0) 185 { 186 t.writelnError("Could not clone vdpm"); 187 return false; 188 } 189 } 190 } 191 with(WorkingDir(buildPath(vitaPath, "vdpm"))) 192 { 193 if(wslExec("which vitasdk-update") != 0 && wslExec(bootstrapVsdk) != 0) 194 { 195 t.writelnError("Could not execute "~bootstrapVsdk); 196 wslExec("sudo rm -rf "~vitaSdkPath); 197 return false; 198 } 199 if(wslExec(installAllVsdk) != 0) 200 { 201 t.writelnError("Could not execute "~installAllVsdk); 202 return false; 203 } 204 } 205 if(wslExec("vitasdk-update") != 0) 206 { 207 t.writelnError("Could not update vitasdk"); 208 return false; 209 } 210 return true; 211 } 212 213 bool setupPsvita(ref Terminal t, ref RealTimeConsoleInput input) 214 { 215 if(!extractToFolder( 216 getHipPath("build", "vita", "hipreme_engine", "hipreme_engine_vita_dev_files.7z"), 217 getHipPath("build", "vita", "hipreme_engine"), 218 t, input 219 )) 220 { 221 t.writelnError("PSVita requires 7zip to extract the development files."); 222 return false; 223 } 224 //https://vitasdk.org/ 225 226 bool ret; 227 version(Windows) ret = setupPsvitaWindows(t, input); 228 else version(linux) ret = setupPsvitaLinux(t, input); 229 else assert(false, "Not supported"); 230 231 if(ret) 232 { 233 configs["vitaSdkPath"] = buildPath(vitaSdkPath, "vitasdk"); 234 configs["firstPsvConfig"] = true; 235 updateConfigFile(); 236 } 237 return ret; 238 239 } 240 241 ChoiceResult preparePSVita(Choice* c, ref Terminal t, ref RealTimeConsoleInput input, in CompilationOptions cOpts) 242 { 243 if(!("firstPsvConfig" in configs) || !configs["firstPsvConfig"].boolean) 244 { 245 if(!setupPsvita(t, input)) 246 return ChoiceResult.Error; 247 } 248 cached(() => timed(t, submoduleLoader.execute(t, input))); 249 import std.string; 250 executeGameRelease(t); 251 putResourcesIn(t, getHipPath("build", "vita", "hipreme_engine", "assets")); 252 253 string dflags = "-I="~configs["hipremeEnginePath"].str~"/modules/d_std/source "~ 254 "-I="~configs["hipremeEnginePath"].str~"/dependencies/runtime/druntime/source "~ 255 "-d-version=PSVita " ~ 256 "-d-version=PSV " ~ 257 "--revert=dtorfields "~ 258 "-mcpu=cortex-a9 "~ 259 "-mattr=+neon,+neonfp,+thumb-mode "~ 260 // "-Os " ~ 261 "-fvisibility=hidden "~ 262 "-float-abi=hard "~ 263 "-gcc="~buildNormalizedPath(configs["vitaSdkPath"].str, "bin", "arm-vita-eabi-gcc")~" "~ 264 "--relocation-model=static "~ 265 "-d-version=CarelessAlocation "~ 266 "-d-version=ArsdUseCustomRuntime "; 267 268 environment["DFLAGS"] = dflags; 269 270 requireConfiguration("psvIp", "Set up PSVita IP for installing your application via FTP.", t, input, (ref str) 271 { 272 str = str.strip(); 273 return isIpAddress(str); 274 }, "psvIp must be a valid IP address"); 275 276 277 requireConfiguration("psvCmdPort", "Set up PSVita Command Port for automatic execution after compilation+installation.", t, input, (ref str) 278 { 279 str = str.strip(); 280 return isNumeric(str); 281 }); 282 283 284 with(WorkingDir(configs["gamePath"].str)) 285 { 286 ProjectDetails d; 287 if(waitRedub(t, DubArguments().command("build").configuration("psvita").arch("armv7a-unknown-unknown-eabi").opts(cOpts), d, 288 getHipPath("build", "vita", "hipreme_engine", "libs")) != 0) 289 { 290 t.writelnError("Could not build for PSVita."); 291 return ChoiceResult.Error; 292 } 293 294 static bool isFirstBuild = true; 295 if(isFirstBuild) 296 { 297 if(!firstBuild(t)) 298 { 299 t.writelnError("Could not build PSVita hipreme_engine.vpk"); 300 return ChoiceResult.Error; 301 } 302 isFirstBuild = false; 303 } 304 else 305 { 306 if(!fastBuild(t)) 307 { 308 t.writelnError("Could not do subsequent builds."); 309 return ChoiceResult.Error; 310 } 311 } 312 } 313 314 return ChoiceResult.None; 315 }